import { readFileSync } from "fs";
import path = require('path');
import urllib = require('urllib');
import {KJUR, hextob64} from 'jsrsasign';
import { IJsapiClientPay, IShuntAmount,IPlaceJSAPI, IShuntReceiversItem, IShuntReqData, IShuntReqResult, IWechatConfigItem, IWechatErrResult, IWechatQueryReq, IWechatQueryResponse, IWechatRefundReq, IWechatRefundReqResult, IWechatShuntItem, IWechatLoginData, IWechatToken, IPlaceResult } from "../interfaces/IWechat.interface";
import { Tools } from "../common/Tools";
import { Provide } from '@midwayjs/decorator';

/**微信支付sdk */
class SDKBase{
    public mchid:string;
    protected apiclient_cert_no:string;
    protected privateKey:string;
    public baseConfig:IWechatConfigItem;

    protected urls = {
        jsapi:() => {
            return {
                url:'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi',
                method:'POST',
                pathname:'/v3/pay/transactions/jsapi',
            }
        },
        app:() => {
            return {
                url:'https://api.mch.weixin.qq.com/v3/pay/transactions/app',
                method:'POST',
                pathname:'/v3/pay/transactions/app',
            }
        },
        h5:() => {
            return {
                url:'https://api.mch.weixin.qq.com/v3/pay/transactions/h5',
                method:'POST',
                pathname:'/v3/pay/transactions/h5',
            }
        },
        native:() => {
            return {
                url:'https://api.mch.weixin.qq.com/v3/pay/transactions/native',
                method:'POST',
                pathname:'/v3/pay/transactions/native',
            }
        },
       
        query:({pathParams}) => {
            return {
                url:`https://api.mch.weixin.qq.com/v3/pay/transactions/id/${pathParams.transaction_id}?mchid=${this.mchid}`,
                method:`GET`,
                pathname:`/v3/pay/transactions/id/${pathParams.transaction_id}?mchid=${this.mchid}`,
            }
        },
        getTransactionsByOutTradeNo:({pathParams}) => {
            return {
                url:`https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${pathParams.out_trade_no}?mchid=${this.mchid}`,
                method:`GET`,
                pathname:`/v3/pay/transactions/out-trade-no/${pathParams.out_trade_no}?mchid=${this.mchid}`,
            }
        },
        close:({pathParams}) => {
            return {
                url:`https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${pathParams.out_trade_no}/close`,
                method:`POST`,
                pathname:`/v3/pay/transactions/out-trade-no/${pathParams.out_trade_no}/close`,
            }
        },
        refund:() => {
            return {
                url:`https://api.mch.weixin.qq.com/v3/refund/domestic/refunds`,
                method:`POST`,
                pathname:`/v3/refund/domestic/refunds`,
            }
        },
        refundQuery:({pathParams}) => {
            return {
                url:`https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/${pathParams.out_refund_no}`,
                method:`GET`,
                pathname:`/v3/refund/domestic/refunds/${pathParams.out_refund_no}`,
            }
        },

        getCertificates:() => {
            return {
                url:`https://api.mch.weixin.qq.com/v3/certificates`,
                method:`GET`,
                pathname:`/v3/certificates`,
            }
        },
        tradebill:({queryParams}) => {
            let {bill_date,bill_type,tar_type} = queryParams;
            return {
                url:`https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date=${bill_date}${bill_type?'&bill_type='+bill_type:''}${tar_type?'&tar_type='+tar_type:''}`,
                method:`GET`,
                pathname:`/v3/bill/tradebill?bill_date=${bill_date}${bill_type?'&bill_type='+bill_type:''}${tar_type?'&tar_type='+tar_type:''}`,
            }
        },
        fundflowbill:({queryParams}) => {
            let {bill_date,account_type,tar_type} = queryParams;
            return {
                url:`https://api.mch.weixin.qq.com/v3/bill/fundflowbill?bill_date=${bill_date}${account_type?'&account_type='+account_type:''}${tar_type?'&tar_type='+tar_type:''}`,
                method:`GET`,
                pathname:`/v3/bill/fundflowbill?bill_date=${bill_date}${account_type?'&account_type='+account_type:''}${tar_type?'&tar_type='+tar_type:''}`,
            }
        },
        downloadbill:({pathParams}) => {
            let url = pathParams;
            let index = url.indexOf('/v3')
            let pathname = url.substr(index)
            return {
                url,
                method:`GET`,
                pathname,
            }
        },

         /**分账 */
         shunt:()=>{
            return {
                url:'https://api.mch.weixin.qq.com/v3/profitsharing/orders',
                method:'POST',
                pathname:'/v3/profitsharing/orders',
            }
        },
        /**分账查询 */
        shuntQuery:({pathParams})=>{
            return {
                url:`https://api.mch.weixin.qq.com/v3/profitsharing/orders/${pathParams.out_order_no}?transaction_id=${pathParams.transaction_id}`,
                method:`GET`,
                pathname:`/v3/profitsharing/orders/${pathParams.out_order_no}?transaction_id=${pathParams.transaction_id}`,
            }
        },
        /**获取待分账余额 */
        getShuntAmount:({pathParams})=>{
            return {
                url:`https://api.mch.weixin.qq.com/v3/profitsharing/transactions/${pathParams.transaction_id}/amounts`,
                method:`GET`,
                pathname:`/v3/profitsharing/transactions/${pathParams.transaction_id}/amounts`,
            }
        },
    }

    public callbackUrl:string;
    /**是否分账 */
    public isShunt:boolean = false;
    // public OAappid:string;
    // public OAsecret:string;
    public secretV3:string;
    public secretV2:string;

    public appid:string;
    public secret:string;
    /**
     * Creates an instance of SDKBase.
     * @param {string} baseConfig 微信的配置信息
     * @param {string} privateKeyPath 微信证书apiclient_key.pem 文件的相对路径
     * @param {*} [urls=null]
     * @memberof SDKBase
     */
    constructor(baseConfig:IWechatConfigItem,privateKeyPath:string= null,urls:any = null){
        if(urls != null && urls != ""){
             this.urls = urls;
        }
        this.appid = baseConfig.appid;
        this.secret = baseConfig.secret;

          //通过appid 获取商户信息.
        this.baseConfig = baseConfig;
        this.mchid = this.baseConfig.mid;
        this.secretV3 = this.baseConfig.secretV3;
        this.secretV2 = this.baseConfig.secretV2;
        // this.OAappid = this.baseConfig.OAappid;
        // this.OAsecret = this.baseConfig.OAsecret;
        this.apiclient_cert_no = this.baseConfig.apiclient_cert_no;
        this.callbackUrl = this.baseConfig.callbackUrl;
        this.isShunt = this.baseConfig.shunt;

        if(privateKeyPath != null && privateKeyPath != null){
            this.privateKey = readFileSync(path.resolve(__dirname,privateKeyPath),'utf-8');
        }
        // this.privateKey = readFileSync(path.resolve(__dirname,'../../../certificate/'+dbname+"/apiclient_key.pem"),'utf-8');
    }


    public async request(urlName:string,bodyParams:any,pathParams:Object=null,queryParams:Object=null):Promise<any>{
        let {url,method,pathname} = this.urls[urlName]({pathParams,queryParams});
        bodyParams = bodyParams == null?"":bodyParams;
        let Authorization = this.makeSign(method,pathname,bodyParams);
        const result = await urllib.request(url,{
                    method:method,
                    dataType:'text',
                    data: method=='GET'?'':bodyParams,
                    timeout: [10000, 15000],
                    headers:{
                        'Content-Type':'application/json',
                        'Accept':'application/json',
                        'Authorization':Authorization
                    }
                })
        // if(result.status == 200){
            return JSON.parse(result.data);
        // }        
        // return result.data;

        // return new Promise((resolve,reject)=>{
        //     let Authorization = this.makeSign(method,pathname,bodyParams);
        //     urllib.request(url,{
        //         method:method,
        //         dataType:'text',
        //         data: method=='GET'?'':bodyParams,
        //         timeout: [10000, 15000],
        //         headers:{
        //             'Content-Type':'application/json',
        //             'Accept':'application/json',
        //             'Authorization':Authorization
        //         }
        //     }).then((result)=>{
        //         let data = JSON.parse(result.data);
        //         if(result.status == 200){
        //             resolve(data);
        //         }else{
        //             reject(data);
        //         }
        //     }).catch(e=>{

        //     })
        // });
    }

    private makeSign(method:string,pathname:string,body:any){
        if(this.privateKey == null || this.apiclient_cert_no == null){
            return "";
        }
        let timestamp = Tools.timestamp;
        let nonce = Tools.generate();
        let bodystr = body&&Object.keys(body).length?JSON.stringify(body):''
        let signature = this.rsaSign(`${method}\n${pathname}\n${timestamp}\n${nonce}\n${bodystr}\n`);
        return `WECHATPAY2-SHA256-RSA2048 mchid="${this.mchid}",nonce_str="${nonce}",timestamp="${timestamp}",signature="${signature}",serial_no="${this.apiclient_cert_no}"`
    }
    /**
    * rsa签名
    * @param content 签名内容
    * @param hash hash算法，SHA256withRSA，SHA1withRSA
    * @returns 返回签名字符串，base64
    */
   rsaSign(content, hash='SHA256withRSA'){
       // 创建 Signature 对象
       const signature = new KJUR.crypto.Signature({
           alg: hash,
       });
       signature.init(this.privateKey);
       signature.updateString(content)
       const signData = signature.sign()
       // 将内容转成base64
       return hextob64(signData)
   }
}

/**微信公众号 */
@Provide()
export class WechatOaSDK extends SDKBase{
    private static _init:WechatOaSDK;
    public static init(baseConfig:IWechatConfigItem,privateKeyPath:string=null):WechatOaSDK{
        if(this._init == null){
            this._init = new WechatOaSDK(baseConfig,privateKeyPath);
        }
        return this._init;
    }
    constructor(baseConfig:IWechatConfigItem,privateKeyPath:string = null){
        super(baseConfig,privateKeyPath,{
            /**将URL 返回给前端. */
            getCode:({pathParams})=>{
                return {
                    url:`https://open.weixin.qq.com/connect/oauth2/authorize?appi=${this.appid}&redirect_uri=${pathParams.host}&response_type=code&scope=${pathParams.scope}&state=STATE#wechat_redirect`,
                    method:`GET`,
                    pathname:`/connect/oauth2/authorize?appi=${this.appid}&redirect_uri=${pathParams.host}&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect`
                }
            },
            getToken:({pathParams})=>{
                return {
                    url:`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${this.appid}&secret=${this.secret}&code=${pathParams.code}&grant_type=authorization_code`,
                    method:`GET`,
                    pathname:`/sns/oauth2/access_token?appid=${this.appid}&secret=${this.secret}&code=${pathParams.code}&grant_type=authorization_code`,
                }
            },
            updateToken:({pathParams})=>{
                return {
                    url:`https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=${this.appid}&grant_type=refresh_token&refresh_token=REFRESH_TOKEN`,
                    method:`GET`,
                    pathname:`/sns/oauth2/refresh_token?appid=${this.appid}&grant_type=refresh_token&refresh_token=REFRESH_TOKEN`,
                }
            },
            checkToken:({pathParams})=>{
                return {
                    url:`https://api.weixin.qq.com/sns/auth?access_token=${pathParams.token}&openid=${pathParams.openid}`,
                    method:`GET`,
                    pathname:`/sns/auth?access_token=${pathParams.token}&openid=${pathParams.openid}`,
                }
            },
            getUserInfo:({pathParams})=>{
                return {
                    url:`https://api.weixin.qq.com/sns/userinfo?access_token=${pathParams.token}&openid=${pathParams.openid}&lang=zh_CN`,
                    method:`GET`,
                    pathname:`/sns/userinfo?access_token=${pathParams.token}&openid=${pathParams.openid}&lang=zh_CNN`,
                }
            }
        });
    }
    /**
     * @param {string} clientUrl
     * @param {string} scope 应用授权作用域，snsapi_base （不弹出授权页面，直接跳转，只能获取用户openid），
     *                                      snsapi_userinfo （弹出授权页面，可通过 openid 拿到昵称、性别、所在地。并且， 即使在未关注的情况下，只要用户授权，也能获取其信息 ）
     * @memberof WechatOaSDK
     */
    public getCode(clientUrl:string,scope:string):string{
       const { url } = this.urls['getCode']({host:clientUrl,scope:scope});
       return url;
    }
    public async getOpenid(code:string):Promise<IWechatToken>{
        let token:IWechatToken = await this.request('getToken',null,{code:code});
        return token;
    }
}

/**微信小程序 */
@Provide()
export class WechatSamllSDK extends SDKBase{
    private static _init:WechatSamllSDK;
    public static init(baseConfig:IWechatConfigItem,privateKeyPath:string):WechatSamllSDK{
        if(this._init == null){
            this._init = new WechatSamllSDK(baseConfig,privateKeyPath);
        }
        return this._init;
    }
    // private _token:string;
    constructor(baseConfig:IWechatConfigItem,privateKeyPath:string = null){
        super(baseConfig,privateKeyPath,{
            getToken:({pathParams})=>{
                return {
                    url:`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${pathParams.appid}&secret=${pathParams.secret}`,
                    method:`GET`,
                    pathname:`/cgi-bin/token?grant_type=client_credential&appid=${pathParams.appid}&secret=${pathParams.secret}`,
                }
            },
            login:({pathParams})=>{
                return {
                    url:`https://api.weixin.qq.com/sns/jscode2session?appid=${pathParams.appid}&secret=${pathParams.secret}&js_code=${pathParams.code}&grant_type=authorization_code`,
                    method:`GET`,
                    pathname:`/sns/jscode2session?appid=${pathParams.appid}&secret=${pathParams.secret}&js_code=${pathParams.code}&grant_type=authorization_code`
                }
            }
        });
    }

    public async getToken():Promise<IWechatToken>{
        let token:IWechatToken = await this.request('getToken',null,{appid:this.baseConfig.appid,secret:this.baseConfig.secret});
        return token;
    }

    public async login(code:string):Promise<IWechatLoginData>{
        let data:IWechatLoginData = await this.request('login',null,{appid:this.baseConfig.appid,secret:this.baseConfig.secret,code:code})
        return data;
    }
}

/**分账操作 */
@Provide()
export class WeChatSubAccount extends SDKBase{
    private static _sub:WeChatSubAccount;
      /**
     * Creates an instance of SDKBase.
     * @param {string} baseConfig 微信的配置信息
     * @param {string} privateKeyPath 微信证书apiclient_key.pem 文件的相对路径
     * @memberof SDKBase
     */
    public static Sub(baseConfig:IWechatConfigItem,privateKeyPath:string):WeChatSubAccount{
        if(this._sub == null){
            this._sub = new WeChatSubAccount(baseConfig,privateKeyPath);
        }
        return this._sub;
    }
    /**
     * Creates an instance of SDKBase.
     * @param {string} baseConfig 微信的配置信息
     * @param {string} privateKeyPath 微信证书apiclient_key.pem 文件的相对路径
     * @memberof SDKBase
     */
     constructor(baseConfig:IWechatConfigItem,privateKeyPath:string){
        super(baseConfig,privateKeyPath);
    }

    /**分账 
     * @param {string} transaction_id 微信订单号 
     * @param {number} price 分账金额（元）
     * @param {boolean} unfreeze  是否解冻剩余金额
    */
     public shunt(transaction_id:string,out_order_no:string,unfreeze:boolean = true):Promise<IShuntReqResult|IWechatErrResult>{
        return new Promise((resolve,reject)=>{
            this.request("getShuntAmount",null,{transaction_id:transaction_id}).then((shuntAmount:IShuntAmount)=>{
                if(shuntAmount.unsplit_amount > 0){
                    let reqdata:IShuntReqData = {
                        appid:this.baseConfig.appid,
                        transaction_id:transaction_id,
                        out_order_no:out_order_no,//Tools.makeNumberId(21),
                        receivers:this.getShuntPrice(shuntAmount.unsplit_amount),
                        unfreeze_unsplit:unfreeze
                    }
                    this.request('shunt',reqdata).then((res:IShuntReqResult)=>{
                        resolve(res);
                    }).catch((err:IWechatErrResult)=>{
                        reject(err);
                    });
                }else{
                }
            }).catch((err:IWechatErrResult)=>{
                reject(err);
            });
            
        });
    }
    /**获取可分账金额 */
    /**分账查询 */
    public shuntQuery(transaction_id:string,out_order_no:string):Promise<IShuntReqResult|IWechatErrResult>{
        return new Promise((resolve,reject)=>{
            this.request('shuntQuery',null,{out_order_no:out_order_no,transaction_id:transaction_id}).then((res:IShuntReqResult)=>{
                resolve(res);
            }).catch((err:IWechatErrResult)=>{
                reject(err);
            });
        });
    }
    /**获取分账信息 
     * @param {int} price 剩余待分账金额(除去手续费) 
    */
    public getShuntPrice(price:number,shuntMerList:IShuntReceiversItem[] = null):IShuntReceiversItem[]{
        let list:IShuntReceiversItem[] = [];
        if(this.baseConfig.shunt == true){
            let childlist:IWechatShuntItem[] = this.baseConfig.shuntList;
            // let poundage = parseFloat((price * this.baseConfig.feeRatio).toFixed(2))*100;//手续费单位分
            for(let i in childlist){
                let shuntdata:IWechatShuntItem = childlist[i];
                if(shuntdata &&childlist[i].isEnable == true){
                    list.push({
                        type:shuntdata.type,
                        description:shuntdata.description,
                        account:shuntdata.account,
                        amount:Tools.formatMoney(price*shuntdata.proportion,false,1)
                    })
                }
            }
        }
        return list;
    }
}



/**微信支付 */
@Provide()
export class WechatPaySDK extends SDKBase{
    public _token:string;
    /**微信商户号 */
    private static _AppSDKList:Map<string,WechatPaySDK> = new Map();
    /**初始化微信SDK信息
     * @param {string} appid 服务器应用id 
     */
    public static init(baseConfig:IWechatConfigItem,privateKeyPath:string):WechatPaySDK{
        let sdk:WechatPaySDK;
        if(!this._AppSDKList.has(baseConfig.mid)){
            sdk = new WechatPaySDK(baseConfig,privateKeyPath);
            this._AppSDKList.set(baseConfig.mid,sdk);
        }else{
            sdk = this._AppSDKList.get(baseConfig.mid);
        }
        return sdk;
    }

     /**
     * Creates an instance of SDKBase.
     * @param {string} baseConfig 微信的配置信息
     * @param {string} privateKeyPath 微信证书apiclient_key.pem 文件的相对路径
     * @memberof SDKBase
     */
    constructor(baseConfig:IWechatConfigItem,privateKeyPath:string){
        super(baseConfig,privateKeyPath);
    }
    /**jsapi 下单 */
    public async jsapi(param:IPlaceJSAPI):Promise<IJsapiClientPay|IWechatErrResult>{
        // return new Promise((resolve,reject)=>{
            param.appid = this.appid;
            param.mchid = this.mchid;
            const result:IPlaceResult = await this.request("jsapi",param);//.then((result:IPlaceResult)=>{
            let ntime = Tools.timestamp;
            if(result.prepay_id != null){
                let clientpay:IJsapiClientPay = {
                    appId:this.baseConfig.OAappid,
                    timeStamp:ntime,
                    nonceStr:Tools.generate(),
                    package:"prepay_id="+result.prepay_id,
                    signType:"RSA",
                }
                clientpay.paySign = this.rsaSign(`${clientpay.appId}\n${clientpay.timeStamp}\n${clientpay.nonceStr}\n${clientpay.package}\n`);
                return clientpay;
            }
            return result as any;
          
                // resolve(clientpay);
            // }).catch((err:IWechatErrResult)=>{
            //     reject(err);
            // });
        // });
    }
    /**查询订单 
     * @param {string} out_trade_no 商户订单号 
    */
    public async PayQuery(out_trade_no:string):Promise<IWechatQueryResponse>{
        // return new Promise((resolve,reject)=>{
            let querydata:IWechatQueryReq = {
                out_trade_no:out_trade_no
            }
            let res:IWechatQueryResponse = await this.request('getTransactionsByOutTradeNo',null,querydata);
            return res;
            // .then((res:IWechatQueryResponse)=>{
            //     resolve(res);
            // }).catch((err:IWechatErrResult)=>{
            //     reject(err);
            // });
        // });
    }
    
    /**获取收款商户信息 */
    public shuntMidList():string{
        let midlist:string = this.baseConfig.mid +"";
        if(this.baseConfig.shunt == true){
            let childlist:IWechatShuntItem[] = this.baseConfig.shuntList;
            for(let i in childlist){
                if(childlist[i].isEnable == true){
                    midlist += ","+childlist[i].account +"/"+childlist[i].proportion;
                }
            }
        }
        return midlist;
    }

    /**退款 */
    public async Refund(data:IWechatRefundReq):Promise<IWechatRefundReqResult|IWechatErrResult>{
        // return new Promise((resolve,reject)=>{
            data.out_refund_no = Tools.makeNumberId(32);
            let result = await this.request('refund',data);
            return result;
            // .then((res:IWechatRefundReqResult)=>{
            //     resolve(res);
            // }).catch((err:IWechatErrResult)=>{
            //     reject(err.message);
            // });
        // });
    }
    /**退款查询 
     * @param {string} out_refund_no 商户退款订单号 
    */
    public RefundQuery(out_refund_no:string):Promise<IWechatRefundReqResult|IWechatErrResult>{
        return new Promise((resolve,reject)=>{
            this.request('refundQuery',null,{out_refund_no:out_refund_no}).then((res:IWechatRefundReqResult)=>{
                resolve(res);
            }).catch((err:IWechatErrResult)=>{
                reject(err.message);
            });
        });
    }
    public getToken(){
        
    }
    public getOpenid(){

    }

}